package dataexplorer;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

class Element{
    private Double[] attributes;
    private Integer clazz;

    public Double[] getAttributes() {
        return attributes;
    }

    public void setAttributes(Double[] attributes) {
        this.attributes = attributes;
    }

    public Integer getClazz() {
        return clazz;
    }

    public void setClazz(Integer clazz) {
        this.clazz = clazz;
    }

    public Element(Double[] attributes, Integer clazz) {
        this.attributes = attributes;
        this.clazz = clazz;
    }
}

class Line{
    private int dimension;
    private double value;
    private int clazz;
    private int direction;

    public Line(int dimension, double value, int clazz, int direction) {
        this.dimension = dimension;
        this.value = value;
        this.clazz = clazz;
        this.direction = direction;
    }

    public int getDimension() {
        return dimension;
    }

    public void setDimension(int dimension) {
        this.dimension = dimension;
    }

    public double getValue() {
        return value;
    }

    public void setValue(double value) {
        this.value = value;
    }

    public int getClazz() {
        return clazz;
    }

    public void setClazz(int clazz) {
        this.clazz = clazz;
    }

    public int getDirection() {
        return direction;
    }

    public void setDirection(int direction) {
        this.direction = direction;
    }    
};

class ElementComparator implements Comparator{
    int dimension;
    
    @Override
    public int compare(Object o1, Object o2) {
        Element e1 = (Element)o1;
        Element e2 = (Element)o2;
        double v1 = (e1.getAttributes())[dimension];
        double v2 = (e2.getAttributes())[dimension];
        if(v1 < v2) {
            return -1;
        }
        if(v1 == v2) {
            return 0;
        }
        return 1;
    }

    public ElementComparator(int dimension) {
        this.dimension = dimension;
    }
}

public class Algorithm {

    private List<Element> data;
    private int dimensions;
    private List<Line> vector;
    private int lastClass;
    private boolean animation;
    private int clicks = 0;

    public int getClicks() {
        return clicks;
    }

    public void setClicks(int clicks) {
        this.clicks = clicks;
    }

    public boolean isAnimation() {
        return animation;
    }

    public void setAnimation(boolean animation) {
        this.animation = animation;
    }
    
    public int getLastClass() {
        return lastClass;
    }

    public void setLastClass(int lastClass) {
        this.lastClass = lastClass;
    }

    public List<Line> getVector() {
        return vector;
    }

    public void setVector(List<Line> vector) {
        this.vector = vector;
    }

    public List<Element> getData() {
        return data;
    }

    public void setData(List<Element> data) {
        this.data = data;
    }

    public int getDimensions() {
        return dimensions;
    }

    public void setDimensions(int dimensions) {
        this.dimensions = dimensions;
    }

    
    
    /**
     * Znajdź najkrótszy wektor binarny dzielący klasy w zbiorze.
     */
    public void generateVector() {
        
        if(data==null)return;
        
        vector = new ArrayList<Line>();
        
        Element[] array = new Element[data.size()];
        
        int k = 0;
        
        for (Element v : data){
            array[k++] = new Element(v.getAttributes(), v.getClazz());
        }

        System.out.println("POCZĄTEK ALGORYTMU");
        
        if(data!=null){
            boolean end = false;
            while(!end){
                System.out.println("Zostało "+array.length+" obiektów");
                int max = 0;
                Line maxLine = null;
                //Dla każdego wymiaru
                for(int i=0; i<dimensions; i++){
                    //Sortuje wg tego wymiaru
                    Arrays.sort(array, new ElementComparator(i));

                    int length = array.length;
                    for(int j=0; !end && j<length; j++){
                        //Jeżeli wszystkie elementy są tej samej klasy
                        if(j==length-1){
                            lastClass = array[0].getClazz();
                            end = true;
                        } else{
                            int v1 = (array[j].getClazz());
                            int v2 = (array[j+1].getClazz());
                            //Jeśli są różne, to jest to miejsce do odcięcia
                            if(v1!=v2){
                                //Jeśli lepsze od aktualnego
                                int z = j;
                                if(Math.abs(array[j].getAttributes()[i]-array[j+1].getAttributes()[i])<1e-8){
                                    while(z>=1 && Math.abs(array[z].getAttributes()[i]-array[z-1].getAttributes()[i])<1e-8){
                                        z--;
                                    }
                                    z--;
                                }
                                if(z+1 > max){
                                    max = z+1;
                                    maxLine = new Line(i, (array[z].getAttributes())[i], array[z].getClazz(), 0);
                                }
                                
                                break;
                            }
                        }
                    }
                    if(end==true)break;
                    //To samo od drugiej strony, przypadek, że wszystkie są takie same został już rozpatrzony
                    length = array.length;
                    for(int j=length-1; j>0; j--){
                        int v1 = (array[j].getClazz());
                        int v2 = (array[j-1].getClazz());
                        if(v1!=v2){
                            int z = j;
                            if(Math.abs(array[j].getAttributes()[i]-array[j-1].getAttributes()[i])<1e-8){
                                while(z+2<length && Math.abs(array[z].getAttributes()[i]-array[z+1].getAttributes()[i])<1e-8){
                                    z++;
                                }
                                z++;
                            }
                            if(length-z > max){
                                max = length-z;
                                maxLine = new Line(i, (array[z].getAttributes())[i], array[z].getClazz(), 1);
                            }
                            break;
                        }
                    }
                }
                if(end==true)break;
                System.out.println("Usuwam "+max);
                Arrays.sort(array, new ElementComparator(maxLine.getDimension()));
                vector.add(maxLine);

                Element[] array2 = null;
                if(maxLine.getDirection() == 0){
                    array2 = Arrays.copyOfRange(array, max, array.length);
                } else{
                    array2 = Arrays.copyOfRange(array, 0, array.length-max);
                }
                array = array2;
            }
            System.out.println("KONIEC ALGORYTMU\n");
            
            System.out.println("ILOŚĆ PROSTYCH: "+vector.size()+"\n");
            for(Line l : vector){
                System.out.println("Wymiar = "+l.getDimension());
                System.out.println("Wartość = "+l.getValue());
                System.out.println("Kierunek = "+l.getDirection());
                System.out.println("Klasa = "+l.getClazz());
                System.out.println("");
            }
            
            System.out.println("ILOŚĆ PROSTYCH: "+vector.size());
        }
    }

    /**
     * Wczytaj dane z pliku.
     */
    public void loadData(String filename) {
        
        vector = null;
        
        // Otwórz plik do odczytu
        FileInputStream file = null;
        
        try {        
            file = new FileInputStream(filename);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, "Nie można otworzyć pliku.", ex);
            System.exit(1);
        }
        
        DataInputStream in = new DataInputStream(file);
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        
        String line;
        
        dimensions = 0;
        
        try {
            while((line = br.readLine()) != null) {   
                // Dane mogą być rozdzielone tabulatorem lub przecinkiem                
                String[] numbers = line.split("(,|\t)");
                
                // Oblicz ilość wymiarów jeśli jeszcze nie istnieje
                if (dimensions == 0) {
                    for (String f : numbers) {
                        ++dimensions;
                    }
                    
                    // Odejmij jedno, ponieważ ostatnia wartość to klasa
                    --dimensions;
                    
                    // Zainicjuj tablicę danych
                    data = new ArrayList<Element>();
                }
                
                // Uzupełnij wiersz
                Double[] row = new Double[dimensions];
                
                for (int i = 0; i < dimensions; i++) {
                    row[i] = new Double(numbers[i]);
                }

                // Dodaj do tablicy
                data.add(new Element(row, Integer.parseInt(numbers[dimensions])));
            }
            
            // Zamknij plik
            in.close();
            
            // Przetestuj wczytane dane
            //testData();
        } catch (IOException ex) {
            Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private void testData() {
        for (int i = 0; i < data.size(); i++) {
            for (Double f : data.get(i).getAttributes()) {
                
                System.out.print(f + " ");
            }
            
            System.out.println(data.get(i).getClazz());
        }
    }

    void showAnimation() {
        animation = true;
    }

    int classify(Double[] row) {
        int clazz = lastClass;
        for(Line l: vector){
            if(l.getDirection() == 0){
                if(row[l.getDimension()] <= l.getValue()){
                    clazz = l.getClazz();
                    break;
                }
            }
            if(l.getDirection() == 1){
                if(row[l.getDimension()] >= l.getValue()){
                    clazz = l.getClazz();
                    break;
                }
            }
        }
        return clazz;
    }
    
}
